/**
 * 
 */
package org.neogenesis.web.dao;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.UserTransaction;
import javax.xml.bind.JAXBException;

import org.jboss.weld.logging.LoggerFactory;
import org.neogenesis.web.Configurations;
import org.neogenesis.web.Utils;
import org.neogenesis.web.loader.MetaPlatform;
import org.neogenesis.web.loader.MetaProbe;
import org.neogenesis.web.loader.MetaSample;
import org.neogenesis.web.loader.ProbesLoader;
import org.neogenesis.web.loader.geoDataseLoader.MINimlLoader;
import org.neogenesis.web.model.Experiment;
import org.neogenesis.web.model.Gene;
import org.neogenesis.web.model.Organism;
import org.neogenesis.web.model.Platform;
import org.neogenesis.web.model.Sample;
import org.neogenesis.web.model.TypePlatform;
import org.neogenesis.web.qualifier.DataRepository;

/**
 * @author fabiofumarola
 * 
 */
@SessionScoped
public class GEOSeriesDAO implements Serializable {

	@Inject
	@DataRepository
	private EntityManager em;

	@Inject
	private PlatformDAO platformDAO;

	@Inject
	private GeneDAO geneDAO;

	@Inject
	private NcbiDAO ncbiDAO;

	@Inject
	private OrganismDAO organismDAO;

	@Inject
	private TypePlatformDAO typePlatformDAO;

	@Inject
	private SampleDAO sampleDAO;

	@Inject
	private ExperimentDAO experimentDAO;

	private Logger log;

	private String webAppPath;

	private MINimlLoader loader;

	private Experiment experiment;

	private String fileGSEPath;

	private String folderGSEPath;

	public GEOSeriesDAO() {

	}

	public boolean init(String fileName) {

		log = Logger.getLogger(this.getClass().toString());

		this.webAppPath = FacesContext.getCurrentInstance()
				.getExternalContext().getRealPath("/");

		try {
			String uploadPath = webAppPath + Configurations.uploadPath;
			// untar the file
			folderGSEPath = Utils.untarFolder(uploadPath, fileName);
			fileGSEPath = uploadPath + fileName;
			String name = Utils.getArchiveName(fileName);
			loader = new MINimlLoader(folderGSEPath, name);
		} catch (IOException e) {
			log.log(Level.SEVERE, e.getMessage());
			return false;
		} catch (JAXBException e) {
			log.log(Level.SEVERE, e.getMessage());
			return false;
		}
		return true;
	}

	public boolean loadPlatforms() {
		Platform platform;
		MetaPlatform metaPlatform;

		Map<String, MetaPlatform> metaPlatforms = loader.getPlatforms();
		for (String platformName : metaPlatforms.keySet()) {

			try {

				metaPlatform = metaPlatforms.get(platformName);
				platform = platformDAO.findByName(platformName);

				if (platform != null) {
					metaPlatform.setPlatform(platform);
					// load the map probe id gene id
					Map<String, Integer> map = platformDAO
							.getMapProbeIdGeneId(platform);
					metaPlatform.setMapProbeIdGeneID(map);

				} else {
					metaPlatform = metaPlatforms.get(platformName);
					platform = metaPlatform.getPlatform();

					// check if organism and TypePlatform exist
					Organism organism = organismDAO.findByName(platform
							.getOrganism().getName());

					if (organism != null)
						platform.setOrganism(organism);

					TypePlatform typePlatform = typePlatformDAO
							.findByName(platform.getTypePlatform().getName());
					if (typePlatform != null)
						platform.setTypePlatform(typePlatform);

					metaPlatform.setPlatform(platform);

					loadProbes(metaPlatform);

					platformDAO.create(platform);
					Logger.getLogger(GEOSeriesDAO.class.toString()).log(
							Level.INFO,
							"inserted platoform with name "
									+ platform.getAccessionName());
				}
			} catch (IOException e) {
				log.log(Level.SEVERE, e.getMessage());
				return false;
			}
		}
		return true;
	}

	public boolean loadExperiment() {
		experiment = loader.getExperiment();
		if (experimentDAO.findByName(experiment.getSeriesIid()) != null)
			return false;

		experimentDAO.create(experiment);
		return true;
	}

	public boolean loadSamples() {
		Sample sample;
		Platform platform;
		MetaPlatform metaPlatform;
		Map<String, Integer> mapProbeIdGeneId;

		// load the samples
		List<MetaSample> metaSamples = loader.getSamples();

		em.getTransaction().begin();

		for (MetaSample metaSample : metaSamples) {

			try {

				// get the reference platform
				metaPlatform = loader.getPlatforms().get(
						metaSample.getPlatformNameRef());
				platform = metaPlatform.getPlatform();

				mapProbeIdGeneId = metaPlatform.getMapProbeIdGeneID();

				sample = metaSample.getSample();
				sample.setExperiment(experiment);
				sample.setPlatformId(platform.getId());

				String sampleFilePath = webAppPath + Configurations.samplesPath
						+ Utils.getUniqueNameSample(sample);

				BufferedWriter file = new BufferedWriter(new FileWriter(
						sampleFilePath));

				// load the probe-has-sample in the db with the observation
				// value
				for (String[] ob : metaSample) {
					if (ob.length == 0)
						continue;
					// get the values
					String probeId = ob[0];
					double intensity = 1f;
					try {
						intensity = Double.parseDouble(ob[1]);
					} catch (NumberFormatException e) {
						System.out.println(ob[1]);
					}
					Integer geneId = mapProbeIdGeneId.get(probeId);
					// add to the buffer
					if (geneId != null) {
						String row = geneId + "\t" + intensity + "\n";
						file.write(row);
					}
				}
				file.flush();
				file.close();
				sample.setObservationFile(sampleFilePath);
				// add the sample to the db
				sampleDAO.create(sample);

				Logger.getLogger(GEOSeriesDAO.class.toString()).log(Level.INFO,
						"inserted sample " + sample.getName());
			} catch (IOException e) {
				em.getTransaction().rollback();
				log.log(Level.SEVERE, e.getMessage());
				return false;
			}
		}
		em.getTransaction().commit();
		return true;
	}

	private void loadProbes(MetaPlatform metaPlatform) throws IOException {
		// load the probes
		ProbesLoader probesLoader = loader.getProbesLoader(metaPlatform
				.getPlatform().getAccessionName());
		metaPlatform.setProbesLoader(probesLoader);

		switch (metaPlatform.getType()) {
		case NONE:

			break;
		case GENE_SYMBOL:
			loadProbesByGeneSymbol(metaPlatform);
			break;
		default:
			// the default is gene id
			loadProbesByGeneId(metaPlatform);
			break;
		}
	}

	private void loadProbesByGeneId(MetaPlatform mPlatform) throws IOException {
		Gene gene = null;

		ProbesLoader probesLoader = mPlatform.getProbesLoader();

		String platformProbesPath = webAppPath + Configurations.platformsPath
				+ Utils.getUniqueNamePlatform(mPlatform.getPlatform());

		List<MetaProbe> toQuery = new LinkedList<MetaProbe>();
		Map<String, Integer> mapProbeIDGeneID = new HashMap<String, Integer>();

		// open the stream to the file
		BufferedWriter out = new BufferedWriter(new FileWriter(
				platformProbesPath));

		for (MetaProbe metaProbe : probesLoader) {
			String line = "";

			if (metaProbe.getGeneId().length() == 0)
				continue;

			gene = geneDAO.find(Integer.parseInt(metaProbe.getGeneId()));
			if (gene == null)
				toQuery.add(metaProbe);
			else {

				String probeId = metaProbe.getProbeId();
				int geneId = gene.getId();
				line = probeId + Configurations.FIELD_SEPARATOR + geneId + "\n";
				out.write(line);
				mapProbeIDGeneID.put(probeId, geneId);
			}
		}

		if (toQuery.size() > 0) {
			ncbiDAO.setBatchSize(1000);
			ncbiDAO.retrieveGenesById(toQuery);
			for (MetaProbe metaProbe : toQuery) {
				String line = "";

				if (metaProbe.getGeneId().length() == 0)
					continue;

				if (metaProbe.getGene() == null)
					continue;

				gene = geneDAO.find(metaProbe.getGene().getId());

				if (gene == null) {
					gene = metaProbe.getGene();
					geneDAO.create(gene);
				}

				String probeId = metaProbe.getProbeId();
				int geneId = gene.getId();
				line = probeId + Configurations.FIELD_SEPARATOR + geneId + "\n";
				out.write(line);
				mapProbeIDGeneID.put(probeId, geneId);
			}
		}
		out.flush();
		out.close();
		Logger.getLogger(GEOSeriesDAO.class.toString()).log(
				Level.INFO,
				"add the probe to platform "
						+ mPlatform.getPlatform().getAccessionName());
		// set the file with the probes
		mPlatform.getPlatform().setProbeFile(platformProbesPath);
		mPlatform.setMapProbeIdGeneID(mapProbeIDGeneID);
	}

	private void loadProbesByGeneSymbol(MetaPlatform metaPlatforms) {
		// TODO Auto-generated method stub

	}

	public void deleteFiles() {
		Utils.deleteFile(fileGSEPath);
		Utils.deleteFolder(folderGSEPath);

	}
}
